/**
 * The contents of this file are subject to the license and copyright detailed in the LICENSE and NOTICE files at the root of the source tree and available online at
 * http://www.askosi.org/license.html
 */
package be.destin.skos.view;

import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.TreeMap;

import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlID;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlTransient;
import javax.xml.bind.annotation.XmlType;

import org.apache.log4j.Logger;

import be.destin.skos.core.Concept;
import be.destin.skos.core.SchemeInterface;
import be.destin.skos.core.SkosManager;
import be.destin.skos.core.TermList;
import be.destin.skos.html.SkosHtmlManager;
import be.destin.skos.search.LabelMatchType;
import be.destin.skos.search.SearchResult;

/**
 * @author Christophe Dupriez
 *
 * This class creates a SAX source to view an authority list only with names appropriates for the user.
 * It also sorts the concepts along those names and filter them (root of a word which must be present in a name) if a filter is specified
 */
@XmlRootElement
@XmlType(propOrder={"top","narrower"})
public class SkosView {

	/** log4j category */
	private static Logger log = Logger.getLogger(SkosView.class);

	public static final String repeatedConcept = " &uarr;";
	private String schemeCode = ""; // List to display
	private SkosViewType viewType = SkosViewType.ALFA;
	private String userLanguage = TermList.anyLanguage; // Language of names to be viewed
	private String filter = "";
	private String broadContext = "";
	private String pivot = ""; // Pivot concept for which all broaders and narrowers are needed
	private HashSet<String> topHandles = null; // Handles of the top terms to start with
    private SkosHtmlManager skosHtmlManager = null;
    private int viewDepth = 0;
    protected HashSet<String> filteredEntries;

	private boolean basketEnabled = false; // Does the caller has loaded the JavaScript skosBasket manager?
	private boolean changed = true;
	private boolean someRejects = false;
	private boolean strictLanguage = false;
	private boolean matchIncluded = false; // Include narrowMatch in other 

	public SkosView () {
    	super();
    }

    /**
     * Constructor with recommended information
     * @param viewType
     * @param userLanguage
     * @param strictLanguage TODO
     * @param schemeCode
     */
    public SkosView (SkosHtmlManager skosHtmlManager, String listCode, SkosViewType viewType, String userLanguage, boolean strictLanguage) {
    	super();
    	this.schemeCode = listCode;
    	this.viewType = viewType;
    	this.userLanguage = userLanguage;
    	this.strictLanguage = strictLanguage;
    	this.setSkosHtmlManager(skosHtmlManager);
    }
    /**
	 * 
	 */
    private int addBroaders(SchemeInterface aScheme, Concept anEntry, int level, Concept[] uppers) {
    	if (level >= (Concept.maxDepth)) return Concept.maxDepth; // recurse a lot but stop one day!
    	int depth = level;
    	boolean looping = false;
    	for (int i=0; i < level; i++) {
    		if (anEntry.getScheme_About().equals(uppers[i].getScheme_About())) {
    			String message = anEntry.getScheme_About()+": is in a hierarchy loop. Depth="+i+" and "+level;
    			log.error(message);
    			anEntry.addError(message);
    			looping = true;
    			break;
    		}
    	}
    	if (!looping) {
        	if (level >= viewDepth) viewDepth = level+1;
        	LinkedList<String> parents = anEntry.getAboutBroader();
    		if (parents != null) {
    			uppers[level] = anEntry;
    			for (String aParent : parents) {
    				if (this.filteredEntries.add(aScheme.getAbout_About(aParent))) { // depth first, not twice the same links
    					Concept aParentEntry = aScheme.getConcept(aParent);
    					if (aParentEntry != null) {
    						int newDepth = addBroaders(aScheme,aParentEntry, level+1, uppers);
    						if (newDepth > depth) depth = newDepth;
    					}
    				}
    			}
    		}
    		parents = anEntry.getScheme_AboutBroadMatch();
    		if (parents != null) {
    			SkosManager skosManager = skosHtmlManager.getSkosManager();
    			uppers[level] = anEntry;
    			for (String aParent : parents) {
    				if (this.filteredEntries.add(aParent)) { // depth first, not twice the same links
    					Concept aParentEntry = skosManager.getScheme_Concept(aParent);
    					if (aParentEntry != null) {
    						int newDepth = addBroaders(aScheme,aParentEntry, level+1, uppers);
    						if (newDepth > depth) depth = newDepth;
    					}
    				}
    			}
    		}
    	}
    	return depth;
    }
    /**
	 * 
	 */

    private void addSibblings(SchemeInterface aList, Concept anEntry) {
    	// Add sibblings AFTER collection broaders and narrowers!
    	LinkedList<String> parents = anEntry.getAboutBroader();
    	if (parents != null) {
    		for (String aParent : parents) {
    			if (this.filteredEntries.contains(aList.getAbout_About(aParent))) {
    				Concept aParentEntry = aList.getConcept(aParent);
    				if (aParentEntry != null) {
    					LinkedList<String> children = aParentEntry.getAboutNarrower();
    					if (children != null) {
    						for (String aChild : children) {
    							this.filteredEntries.add(aList.getAbout_About(aChild));
    						}
    					}
    					children = anEntry.getScheme_AboutNarrowMatch();
    					if (children != null) {
    						for (String aChild : children) {
    							this.filteredEntries.add(aChild);
    						}
    					}
    				}
    			}
    		}
    	}
    	parents = anEntry.getScheme_AboutBroadMatch();
    	if (parents != null) {
			SkosManager skosManager = skosHtmlManager.getSkosManager();
    		for (String aParent : parents) {
    			if (this.filteredEntries.contains(aParent)) {
					Concept aParentEntry = skosManager.getScheme_Concept(aParent);
    				if (aParentEntry != null) {
    					LinkedList<String> children = aParentEntry.getAboutNarrower();
    					if (children != null) {
    						for (String aChild : children) {
    							this.filteredEntries.add(aList.getAbout_About(aChild));
    						}
    					}
    					children = anEntry.getScheme_AboutNarrowMatch();
    					if (children != null) {
    						for (String aChild : children) {
    							this.filteredEntries.add(aChild);
    						}
    					}
    				}
    			}
    		}
    	}
    }

    /**
	 * 
	 */
    private void addNarrowers(SchemeInterface aScheme, Concept anEntry, int level, Concept[] uppers) {
    	if (level >= (Concept.maxDepth)) return; // recurse a lot but stop one day!
    	boolean looping = false;
    	for (int i=0; i < level; i++) {
    		if (anEntry.getAbout().equals(uppers[i].getAbout())) {
    			String message = anEntry.getScheme_About()+": is in a hierarchy loop. Depth="+i+" and "+level;
    			log.error(message);
    			anEntry.addError(message);
    			looping = true;
    			break;
    		}
    	}
    	if (looping) return;
    	LinkedList<String> children = anEntry.getAboutNarrower();
    	if (children != null) {
    		uppers[level] = anEntry;
    		for (String aChild : children) {
    			if (this.filteredEntries.add(aScheme.getAbout_About(aChild))) { // depth first, not twice the same links
    				Concept aChildEntry = aScheme.getConcept(aChild);
    				if (aChildEntry != null) {
    					addNarrowers(aScheme,aChildEntry, level+1,uppers);
    				}
    			}
    		}
    	}
    	children = anEntry.getScheme_AboutNarrowMatch();
    	if (children != null) {
    		uppers[level] = anEntry;
    		for (String aChild : children) {
    			if (this.filteredEntries.add(aChild)) { // depth first, not twice the same links
    				Concept aChildEntry = skosHtmlManager.getSkosManager().getScheme_Concept(aChild);
    				if (aChildEntry != null) {
    					addNarrowers(aScheme,aChildEntry, level+1,uppers);
    				}
    			}
    		}
    	}
    }
	/**
	 * 
	 */
	private void eval() {
		if (!changed) return;
		this.filteredEntries = null;
		Concept[] uppers = new Concept[Concept.maxDepth];
		if (this.filter != null && this.filter.length() > 0) { //Find all concepts containing the filter
			SchemeInterface aList = skosHtmlManager.getSkosManager().getScheme(this.schemeCode);
			this.filteredEntries = new HashSet<String>();
			//String FILTER = Util.noAccent(this.filter.toUpperCase());
			Collection<SearchResult> allEntries = aList.labelMatch(this.filter,userLanguage, strictLanguage, LabelMatchType.ANYWHERE);
			for (Concept anEntry : skosHtmlManager.getSkosManager().getMatchingConcepts(allEntries)) {
/* Now implemented with labelMatch
				String label = anEntry.getPrefLabel(this.userLanguage);
				if (label != null && !label.isEmpty()) {
					String LABEL = Util.noAccent(label.toUpperCase());
					if (LABEL.indexOf(FILTER) >= 0) { */
						if (this.filteredEntries.add(anEntry.getScheme_About())) {
							addBroaders(aList, anEntry, 0,uppers);
						}
/*
					}
				}
*/
			}
			//log.info("Filter="+this.filter+",entries="+filteredEntries.size());
		} else if (pivot != null && !pivot.isEmpty()) {
			SchemeInterface aList = skosHtmlManager.getSkosManager().getScheme(this.schemeCode);
			this.filteredEntries = new HashSet<String>();
			this.filteredEntries.add(aList.getAbout_About(pivot));
			Concept anEntry = aList.getConcept(pivot);
			int depth = addBroaders(aList, anEntry, 0, uppers);
			if (depth != 0) {
				uppers[0] = uppers[depth]; // anEntry will be put at uppers[depth]!
			}
			addNarrowers(aList, anEntry, depth,uppers);
			addSibblings(aList, anEntry);
		}
		changed = false;
	}
	/**
	 * @return the children of the top: the top of the SkosView!
	 */
	public LinkedList<ViewConcept> getNarrower() {
    	someRejects = false;
		if (skosHtmlManager == null) return null;
		eval();
		SchemeInterface aList = skosHtmlManager.getSkosManager().getScheme(this.schemeCode);
		if (aList == null) return null;
		TreeMap<String,ViewConcept> sorter = new TreeMap<String,ViewConcept>();
		Collection<Concept> baseChildren = null;
		boolean noTop = (topHandles == null) || topHandles.isEmpty();
		if (noTop) {
			if (filteredEntries == null) {
				baseChildren = aList.getAllConcepts();
			}
			else {
				baseChildren = skosHtmlManager.getSkosManager().getConcept(filteredEntries);
			}
		} else {
			baseChildren = aList.getConcept(topHandles);
		}
		if (baseChildren != null) {
			for (Concept aChild : baseChildren) {
				if (filteredEntries == null || filteredEntries.contains(aChild.getScheme_About())) {
					String label = aChild.getSortLabel(userLanguage);
					if (this.viewType != SkosViewType.TREE || (!noTop)) {
						sorter.put(label,new ViewConcept (this, aChild,1) );
					} else { // Collect all concepts without Broader and BroadMatch (to start with them...)
						LinkedList<String> parents = aChild.getAboutBroader();
						if (parents == null || parents.isEmpty()) {
							parents = aChild.getScheme_AboutBroadMatch();
							if (parents == null || parents.isEmpty()) {
								sorter.put(label,new ViewConcept (this, aChild,1) );
							}
						}
					}
				} else someRejects = true;
			}
		}
		LinkedList<ViewConcept> children = new LinkedList<ViewConcept>();
		for (ViewConcept aViewConcept : sorter.values()) {
			children.add(aViewConcept);
		}
		return children;
	}
	/**
	 * @return the schemeCode
	 */
    @XmlAttribute(required = true)
    @XmlID
	public String getAbout() {
		return schemeCode;
	}
	/**
	 * @return the maxDepth
	 */
	@XmlAttribute
	public int getDepth() {
		return viewDepth;
	}
	/**
	 * @return the filter
	 */
	@XmlAttribute
	public String getFilter() {
		return filter;
	}
	/**
	 * @return the userLanguage
	 */
	@XmlAttribute(required = true)
	public String getLang() {
		return userLanguage;
	}
	/**
	 * @return the topHandles
	 */
	public HashSet<String> getTop() {
		if (topHandles == null) topHandles = new HashSet<String>();
		return topHandles;
	}
	/**
	 * @return the viewType
	 */
	@XmlAttribute(required = true)
	public SkosViewType getType() {
		return viewType;
	}

	/**
	 * @return the maxDepth
	 */
	@XmlAttribute
	public boolean isBasket() {
		return basketEnabled;
	}
	/**
	 * @param children
	 *            the children to set
	 */
	public void setNarrower(LinkedList<ViewConcept> child) {
//		USELESS!
//		SchemeInterface aList = SkosManager.getList(this.listCode);
//		LinkedList<Concept> children = new LinkedList<Concept>();
//		for (ViewConcept aChild : child) {
//			children.add(aList.getEntry(aChild.getCode()));
//		}
//		aList.setEntries(children);
	}
	/**
	 * @param schemeCode the schemeCode to set
	 */
	public void setAbout(String listCode) {
		if (listCode == null) listCode = "";
		changed = changed || ( ! this.schemeCode.equals(listCode));
		this.schemeCode = listCode;
	}
	/**
	 * @param maxDepth the maxDepth to set
	 */
	public void setDepth(int maxDepth) {
		changed = changed || (this.viewDepth != maxDepth);
		this.viewDepth = maxDepth;
	}

	/**
	 * @param filter the filter to set
	 */
	public void setFilter(String filter) {
		if (filter == null) filter = "";
		changed = changed || ( ! this.filter.equals(filter));
		this.filter = filter;
	}

	/**
	 * @param userLanguage the userLanguage to set
	 */
	public void setLang(String userLanguage) {
		if (userLanguage == null) userLanguage = TermList.anyLanguage;
		changed = changed || ( ! this.userLanguage.equals(userLanguage));
		this.userLanguage = userLanguage;
	}


	/**
	 * @param topHandles the topHandles to set
	 */
	public void setTop(HashSet<String> topHandles) {
		this.topHandles = topHandles;
		changed = true;
	}

	/**
	 * @param viewType the viewType to set
	 */
	public void setType(SkosViewType viewType) {
		changed = changed || (this.viewType != viewType);
		this.viewType = viewType;
	}

	/**
	 * @param enabled: skosBasket JavaScript library available?
	 */
	public void setBasket(boolean enabled) {
		this.basketEnabled = enabled;
	}

	public String toString() {
		return "[" + schemeCode + "("+userLanguage+")" + viewType
				+ ",F=" + filter
				+ (filteredEntries != null && !filteredEntries.isEmpty() ? "("+filteredEntries.size()+")" : "")
				+ "<"+viewDepth+"]";
	}

	/**
	 * @return the skosManager
	 */
	@XmlTransient
	public SkosHtmlManager getSkosHtmlManager() {
		return skosHtmlManager;
	}

	/**
	 * @param skosManager the skosManager to set
	 */
	public void setSkosHtmlManager(SkosHtmlManager skosHtmlManager) {
		this.skosHtmlManager = skosHtmlManager;
	}

	/**
	 * @return the pivot
	 */
	@XmlTransient
	public String getPivot() {
		return pivot;
	}

	/**
	 * @param pivot the pivot to set
	 */
	public void setPivot(String pivot) {
		this.pivot = pivot;
	}

	public int addConceptList(LinkedList<ViewConcept> narrowers, LinkedList<ViewConcept> written) {
		int total = 0;
		if (narrowers != null && !narrowers.isEmpty()) {
			String pivot = this.getPivot();
			if (pivot==null) pivot = "";
			for (ViewConcept aViewConcept: narrowers) {
				if ( ! written.contains(aViewConcept)) {
					written.add(aViewConcept);
					total += this.addConceptList(aViewConcept.getNarrower(), written);
				}
			}
		}
		return total;
	}


	@XmlTransient
    public boolean isSomeRejects() {
		return someRejects;
	}

	public void setSomeRejects(boolean someRejects) {
		this.someRejects = someRejects;
	}

	public boolean hasBroadContext() {
		return broadContext != null && !broadContext.isEmpty();
	}

	@XmlTransient
	public String getBroadContext() {
		return broadContext;
	}

	public void setBroadContext(String broadContext) {
		this.broadContext = broadContext;
	}

	/**
	 * @return the strictLanguage
	 */
	@XmlAttribute
	public boolean isStrictLanguage() {
		return strictLanguage;
	}

	/**
	 * @param strictLanguage the strictLanguage to set
	 */
	public void setStrictLanguage(boolean strictLanguage) {
		this.strictLanguage = strictLanguage;
	}
}
